package edu.pku.sei.ocl.utilities;

import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.UniqueEList;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;

import edu.pku.sei.ocl.evaluation.Context;
import edu.pku.sei.ocl.evaluation.OCLVariable;

public class ChangeLog {
	public void reset(){
		createdObject.clear();
		deletedObject.clear();
		createdLink.clear();
		deletedLink.clear();
		changedSlot.clear();
		modifiedObject.clear();
		
		newTimeStamp();
		
	}
	
	public void newTimeStamp() {
		modifiedObjectFromLastTimestamp.clear();
	}
	
	
	private EList<EList<EObject>> modificationHistory = new BasicEList<EList<EObject>>();
	
	private EList<EObject> createdObject = new UniqueEList<EObject>(5);
	private EList<EObject> deletedObject = new UniqueEList<EObject>(5);
	private EList<ELink> createdLink = new UniqueEList<ELink>(5);
	private EList<ELink> deletedLink = new UniqueEList<ELink>(5);
	private EList<ESlot> changedSlot = new UniqueEList<ESlot>(5);
	private EList<EObject> modifiedObject = new UniqueEList<EObject>(5);
	private EList<EObject> modifiedObjectFromLastTimestamp = new UniqueEList<EObject>(5);
	
	//private CacheUtil changedVariable = new CacheUtil();//key=context+variable
	
	final class ELink {
		public ELink(EObject objA, EObject objB, EReference refA,
				EReference refB) {
			super();
			this.objA = objA;
			this.objB = objB;
			this.refA = refA;
			this.refB = refB;
		}
		public EObject objA;
		public EObject objB;
		public EReference refA;
		public EReference refB;
		
		public boolean equals(Object r){
			if(r instanceof ELink){
				ELink l = (ELink)r;
				
				return ((objA==l.objA&&objB==l.objB&&refA==l.refA&&refB==l.refB)
						||(objB==l.objA&&objA==l.objB&&refB==l.refA&&refA==l.refB));
			}
			return false;
		}
	}
	final class ESlot {
		
		public ESlot(EObject obj, EAttribute property) {
			super();
			this.obj = obj;
			this.property = property;
		}
		public ESlot(EObject obj, EAttribute property, Object oldValue) {
			super();
			this.obj = obj;
			this.property = property;
			this.oldValue = oldValue;
		}
		public EObject obj;
		public EAttribute property;
		public Object oldValue;
		
		public boolean equals(Object l){
			if(l instanceof ESlot){
				ESlot r = (ESlot) l;
				return (obj==r.obj&&property==r.property);
			}
			return false;
		}
	}
	
	public Object starttime = null;
	
	public boolean oclIsDelete(EObject obj) {
		return deletedObject.contains(obj);
	}
	public boolean oclIsDelete(EObject objA, EObject objB, EReference refA){
		ELink link = new ELink(objA,objB,refA,refA.getEOpposite());
		return deletedLink.contains(link);
	}
	
	public void delete(EObject obj){
		deletedObject.add(obj);
	}
	public void delete(EObject objA,EObject objB, EReference refA){
		ELink link = new ELink(objA,objB,refA,refA.getEOpposite());
		deletedLink.add(link);
		modifiedObject.add(objA);
		modifiedObject.add(objB);
		
		modifiedObjectFromLastTimestamp.add(objA);
		modifiedObjectFromLastTimestamp.add(objB);
	}
	
	public boolean oclIsNew(EObject obj){
		return createdObject.contains(obj);
	}
	public boolean oclIsNew(EObject objA, EObject objB, EReference refA){
		ELink link = new ELink(objA,objB,refA,refA.getEOpposite());
		
		return createdLink.contains(link);
	}
	
	public void create(EObject obj){
		createdObject.add(obj);
		modifiedObject.add(obj);
		
		modifiedObjectFromLastTimestamp.add(obj);
	}
	public void create(EObject objA,EObject objB, EReference refA){
		ELink link = new ELink(objA,objB,refA,refA.getEOpposite());
		createdLink.add(link);
		modifiedObject.add(objA);
		modifiedObject.add(objB);
		
		modifiedObjectFromLastTimestamp.add(objA);
		modifiedObjectFromLastTimestamp.add(objB);
	}
	
	public void change(EObject obj, EAttribute attr, Object oldValue){
		ESlot slot = new ESlot(obj,attr,oldValue);
		changedSlot.add(slot);
		modifiedObject.add(obj);
		modifiedObjectFromLastTimestamp.add(obj);
	}
//	public void change(Context context, OCLVariable var,Object oldValue){
//		if(oldValue==null)
//			oldValue = OCLConstant.OCLNull;
//		changedVariable.put(oldValue, context,var);
//	}
	public boolean oclIsChanged(EObject obj){
		return modifiedObject.contains(obj);
	}
	
	public boolean oclIsChangedFromLastTimestamp(EObject obj) {
		return  modifiedObjectFromLastTimestamp.contains(obj);
	}
	
	public boolean oclIsChanged(EObject obj, EAttribute attr){
		ESlot slot = new ESlot(obj,attr);
		return changedSlot.contains(slot);
	}
	public boolean oclIsChanged(EObject obj, String attrName){
		EClass cls = obj.eClass();
		EAttribute attr = (EAttribute) cls.getEStructuralFeature(attrName);
		return oclIsChanged(obj,attr);
	}
	public boolean oclIsChanged(Context context,OCLVariable var){
		Object value = context.getValue(var);
		return  OCLStandardLibrary.oclIsValid(value);
	}
	public boolean oclIsChanged(Context context,String varName){
		OCLVariable var = context.getVariable(varName);
		if(var!=null)
			return  oclIsChanged(context,var);
		else return true;
	}
	
}
